管理 API 可以說是前後端一直以來的難題之一。
後端要實作正確的 API,並且針對不同的格式做處理,同時也要防範 CORS 以及 DDoS 等等;而前端管理 API,要如何管理參數的正確性,endpoint 如何有效地整合在一起,如果請求有錯誤,又要怎麼處理,才不會讓整個 UI 機制都垮掉,都是與後端串接 API 時需要考慮的地方。
在實作 API 的時候,有幾件事情要注意,不光是只是 call API 而已:
以下我們來探討這些常見的問題。
這個情況可以透過 1. nginx 設定請求時間 2. 應用層將每個請求都建立超時時間。
如果很不幸與你合作的後端沒有實作這些功能,剛好 SQL 又寫得很爛的話,你可能需要處理 API 超時的狀況,以免讓使用者等太久。
在 ajax 當中,我們可以用 xhr.abort()
來取消一個請求,最簡單的方法就是 setTimeout(() => xhr.abort(), REQUEST_TIMEOUT)
放棄請求。另外,也需要在 UI 上提示使用者,這次的請求不成功,並顯示相關的原因。
fetch
在 chrome69 中推出了 AbortController
,詳細可以參考我之前寫的文章
以前 fetch
無法直接取消請求,不過現在有了 abortController,就可以取消請求了。
在實作這類型的功能時,你或許會需要提示使用者,現在這個請求超時,並提供使用者幾個選項:1. 錯誤訊息提示 2. 提供重試按鈕
特別要提一下的是 fetch()
第二個參數中的 headers
可以透過 Headers()
介面來實作。雖然用物件也可以啦。
還有 body 在傳入 application/json
要記得使用 JSON.stringify
。
這邊要注意的是如果 fetch
回傳像是 404 等錯誤代碼,是不會進入 catch
的,也就是這個 Promise 還是會直接 resolved,只有在 network error 的時候 fetch 才會失敗。
像是下面的範例中:
fetch('/not-exist-api-path')
.then(console.log) // 會執行
.catch(console.warn); // 就算回傳 404 也不會 catch
如果出現錯誤,一樣會繼續執行 then
,所以如果你希望將 4xx, 5xx 等系列的狀態碼當作錯誤的話,需要再另外判斷:
fetch('/not-exist-api-path')
.then(res => {
if (res.ok) { // 可以透過 res.ok 來判斷回應是否正常
// res.status: 404
// res.statusText: Not Found
return res.json()
}
throw res; // 把 res 丟出去
});
或者直接套用成熟的函式庫來減少 API 管理的複雜度。目前比較熱門的套件是 axios,除了 API 很簡潔之外,也很容易做客製化的設定。
HTTP 狀態碼是用來規範伺服器狀態的,以 3 位數的數字表示對應的訊息。
本篇幅只介紹錯誤狀態的部分。
錯誤狀態代碼很多,不過簡單可以區分為 4xx 系列與 5xx 系列。通常 5xx 系列代表後端的程式碼有問題或伺服器有問題導致的錯誤,;而 4xx 代表你的請求有問題,導致伺服器不接受請求。
常見的狀況有:
400
: bad request,可能是參數有誤,或是必要的參數未填,或是請求有誤等等,可以參考後端提供了 error 欄位來查看。這時你可能需要提示使用者,哪裡出錯了,並且最好能夠保留使用者的輸入,以便他們修改。401
: Unauthorized,你的請求不被允許,最常見的情況就是沒有登入,或是你本來就沒有權限,也有可能是你忘記送出 cookie 或 token。不妨確認一下登入狀態,並且告知使用者發生了什麼事。這類型的錯誤通常是權限不足,直接將使用者導到正確的頁面(例如登入頁面)也行。403
: Forbidden,你不允許訪問這個資源。429
: Too many requests: 表示撞到 rate limit500
: Internal Server Error 伺服器遇到不明確的錯誤,所以無法完成請求。通常是某段程式碼導致例外而沒有被正確處理。理論上我們可以將所有的回應都回傳 200,再從 response 當中判斷錯誤,像是 GraphQL 就是統一一個 endpoint 再處理。
不過透過錯誤狀態代碼我們可以更清楚知道發生什麼事,瀏覽器也可以針對狀態代碼顯示不同的錯誤訊息,而且這是經過標準化後的代號。
如果你的應用程式(像部落格文章等),可能會因為無法送出 API 而導致文章沒有送出,結果要重打一遍的情況。
要實作即時儲存機制有兩種辦法,一種是直接存在瀏覽器端的 localStorge
,並且在送出的時候刪掉;另外一種則是每隔一段時間就自動打 API 到後端幫使用者儲存。
在這種情況下,你可能需要讓使用者感受到時時刻刻都有儲存的感覺,像是 google drive 文件中的即時儲存或是 medium 的即時儲存
如果使用者在離線時(可能因為網路問題斷線)做了一些必須連線才可以做的操作,我們可能需要將使用者的「操作行為」記錄下來,並且在 online 恢復連線的時候,一一將使用者的操作送出。
此時你可能需要 localStorge
或是 sessionStorge
來記錄,這種類型的方式適合用物件來描述行為,因為很容易做序列化。
同時可能需要監聽 navigator.onLine
或是查看 navigator.online
這個值。如果偵測到目前沒有連線,可以顯示對應的訊息。
在 online
的時候,可能需要通知使用者,目前連線狀態已恢復,是否要再次送出。
有時候 API 無效可能只是伺服器太忙碌或是其他因素,對於這類型的 API,我們可以使用 retry 的方式。
retry 又可以分為兩種:1. 提供重試選項(按鈕)讓使用者觸發。 2. 自動在 retry,超過一定次數後再通知使用者。
如果是第 2 種方式,通常會使用 exponential backoff,也就是指數退後的方式。什麼是指數退後呢?
一般來說,如果 API 失敗我們想要重試的話,會每隔一段時間再重試一次,這個區間如果太小,可能會讓伺服器負擔太大;如果區間太大,又會讓使用者等太久。因此指數退後就是以指數函數的方式,先從間隔較短開始,1 秒、2 秒、4 秒、8 秒逐漸增加。
最近 GraphQL 盛行,大幅度減緩了前端管理 API 的難題,我們可以預測回傳的欄位及型別,也不需要用狀態碼來判斷錯誤(是好是壞呢?),而是統一一個 endpoint,並且由回傳的欄位查看是否有錯誤。
關於 GraphQL,我們會在其他章節討論。
設計、使用 API 時有以下情形需要考慮:
另外在這篇文章中我們只講到概念上要考慮哪些事情,但並沒有真正實作,等到後面進階篇時會再搭配 RxJS 一起介紹。